package edu.wpi.cs509.ui.simulators;

import java.util.Random;
import java.util.Timer;

import edu.wpi.cs509.clientlogic.UserSession;
import edu.wpi.cs509.common.exceptions.IllegalMove;
import edu.wpi.cs509.common.exceptions.InitialPlacementError;
import edu.wpi.cs509.common.exceptions.InvalidLocation;
import edu.wpi.cs509.common.exceptions.PieceNotFound;
import edu.wpi.cs509.representation.Location;
import edu.wpi.cs509.representation.PlayerPiece;
import edu.wpi.cs509.representation.TeamColor;
import edu.wpi.cs509.ui.intfc.GameEventNotify;
import edu.wpi.cs509.ui.intfc.GameInterface;


/**
 * The game object used by the UI Client Logic simulator
 * 
 * @author afoltan
 *
 */
public class ClientGame implements GameInterface {

	// for randomization
	private static final Random R = new Random();
		
	private static final int DELAY = 100;
	private static final int DELAY_VARIABILTY = 200;
	
	// the server game instance
	private ServerGame sGame;
	
	// set later
	private GameEventNotify notify;	
	private UserSession player;
	private boolean ready;
	
	// default constructor not permitted
	@SuppressWarnings("unused")
	private ClientGame() {}

	/**
	 * Constructor
	 * @param notify - game event notification handler
	 */
	public ClientGame( ServerGame sGame, UserSession player, GameEventNotify notify ) {
		this.player = player;
		this.notify = notify;
		this.ready = false;
		this.sGame = sGame;
	}
	

	/**
	 * Delay provides a randomized time delay in msec that can
	 * be used by the timer.schedule() method to simulate round-trip
	 * messaging delays to the server.
	 * @return
	 */
	private int delay() {
		int d;
		d = DELAY + R.nextInt(DELAY_VARIABILTY);
		return d;
	}

	/**
	 * Trace support
	 */
	private void trace(String func) {
		System.out.println("UI ----- "+func+" ----> ClientLogic");
	}
	
	public GameEventNotify getNotify() {
		return notify;
	}

	/**
	 * Called by the UI to place a piece from the graveyard onto the board.
	 * @param loc - the board location to place the piece on
	 * @param piece - the piece from the gaveyard to place
	 */
	@Override
	public void placePiece( Location loc, PlayerPiece piece ) throws IllegalMove, InvalidLocation {
		trace("placePiece()");

		if (loc == null) {
			throw new InvalidLocation("NULL location specified in placePiece()");
		}

		if (!loc.isValidBoardLocation()) {
			throw new InvalidLocation("Location "+loc.toString()+" location specified");
		}
		
		if (piece == null) {
			throw new IllegalMove("NULL piece specified in placePiece()");
		}
		
		if (!isSquareEmpty(loc)) {
			System.out.println("Attempting to place piece "+piece.getName()+" at occupied location "+loc.toString());
			throw new IllegalMove("A piece is already on that square");
		}
		
		sGame.putPiece( loc, piece );
		
		new Timer().schedule( new PiecePlacedNotify(notify, piece, loc), delay() );
		
		// Also need to notify opponent
		if (sGame.getOpponentNotify(this) != null) {
			sGame.getOpponentNotify(this).notifyPiecePlaced(piece, loc);
		}
	}

	/**
	 * 
	 */
	@Override
	public void move(Location from, Location to)
			throws IllegalMove {
		trace("move()");

		if (from == null) {
			throw new IllegalMove("NULL 'from' location specified in move()");
		}

		if (!from.isValidBoardLocation()) {
			throw new IllegalMove("Location 'from'="+from.toString()+" is invalid");
		}

		if (to == null) {
			throw new IllegalMove("NULL 'to' location specified in move()");
		}

		if (!to.isValidBoardLocation()) {
			throw new IllegalMove("Location 'to'="+to.toString()+" is invalid");
		}

		try {
			if (isSquareEmpty(from)) {
				throw new IllegalMove("No piece in FROM location ["+
					from.getXCoordinate()+","+from.getYCoordinate()+"]");
			}

			if (isSquareEmpty(to)) {
				sGame.movePiece(this,from,to);
			} else {
				sGame.attackPiece(this,from,to);
			}
		} catch ( InvalidLocation e ) {
			System.out.println("Invalid board location specified from=["+
					from.getXCoordinate()+","+from.getYCoordinate()+"] to=["+
					to.getXCoordinate()+","+to.getYCoordinate()+"]" );
		}
		
	}

	@Override
	public void ready() throws InitialPlacementError {
		trace("ready()");
		ready = true;
		sGame.signalPlayerReady(this);		
	}

	@Override
	public void quit() {
		trace("quit()");
		// TODO Auto-generated method stub
		
	}

	//
	//  Game Object Accessors
	//
	
	@Override
	public boolean isSquareEmpty(Location loc) throws InvalidLocation {
		return sGame.isSquareEmpty(loc);
	}

	@Override
	public PlayerPiece getPiece(Location loc) throws PieceNotFound {
		return sGame.getBoardPiece(loc);
	}

	@Override
	public long getGameID() {
		return sGame.getGameID();
	}

	/**
	 * Returns the player name for the specified side.
	 * @return String - name of the player or NULL in the case
	 * where the player has not joined the game yet.
	 */
	@Override
	public String getMyName() {
		UserSessionSim p = (UserSessionSim)player;
		return p.getUsername();
	}
	
	@Override
	public String getOpponentName() {
		return sGame.getOpponentName(this);
	}
	
	@Override
	public TeamColor getMyColor() {
		return sGame.getTeamColor(this);
	}

	@Override
	public TeamColor getOpponentColor() {
		return sGame.getOpponentColor(this);
	}

	@Override
	public boolean isJoinable() {
		return sGame.isJoinable();
	}

	@Override
	public boolean isObservable() {
		return sGame.isObservable();
	}

	public boolean isReady() {
		return ready;
	}

	/**
	 * Notifies the player that their opponent has readied-up.
	 */
	void notifyOpponentReady() {
		new Timer().schedule( new OpponentReadyNotfiy(notify), delay() );
	}

	@Override
	public void removePiece(Location loc) throws IllegalMove, InvalidLocation {
		// TODO Auto-generated method stub
		trace("removePiece()");

		if (isSquareEmpty(loc)) {
			System.out.println("Attempting to remove piece from location "+loc.toString());
			throw new IllegalMove("Removing piece from empty square");
		}
		
		//new Timer().schedule( new PiecePlacedNotify(notify, loc), delay() );
		notify.notifyPieceRemoved(sGame.getBoardPiece(loc), loc);

		// Also need to notify opponent
		sGame.getOpponentNotify(this).notifyPieceRemoved(sGame.getBoardPiece(loc), loc);
		

		// Remove the piece from the server gameboard
		sGame.removePiece( loc );
		
	}
	
	public ServerGame getServerGame() {
		return sGame;
	}

	@Override
	public boolean obfuscatePieces() {
		// TODO Auto-generated method stub
		return false;
	}
	
	@Override
	public boolean gameIsObserver() {
		return false;
	}
}
